package ConveyorBelt.Map;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.tiled.TiledMap;
import org.newdawn.slick.util.pathfinding.AStarPathFinder;
import org.newdawn.slick.util.pathfinding.Mover;
import org.newdawn.slick.util.pathfinding.Path;
import org.newdawn.slick.util.pathfinding.PathFindingContext;
import org.newdawn.slick.util.pathfinding.TileBasedMap;

import ConveyorBelt.Actors.Block;
import ConveyorBelt.Actors.Destination;
import ConveyorBelt.Actors.Dummy;
import ConveyorBelt.Actors.Switch;
import ConveyorBelt.Base.Body;
import ConveyorBelt.Base.Entity;
import ConveyorBelt.Base.Level;
import ConveyorBelt.Brain.DummyBrain;

public class Map implements Entity, TileBasedMap {
	
	private TiledMap map = null;
	public int mapX, mapY;
	private int tileID;
	private enum layer {
		TERRAIN, ENTITY, DECORATE
	}
	public boolean[][] collisionMatrix;
	public Shape startPosition;
	public static Vector<Body> blockEntities;
	public static Vector<Body> destinationEntities;
	public static Vector<Body> switchEntities;
	public static Queue<Dummy> dummyQueue;
	
	public Map(String path) throws SlickException {
		map = new TiledMap(path);
		init();
	}
	
	public void init() throws SlickException {
		mapX = 240;
		mapY = 20;
		dummyQueue = new LinkedList<Dummy>();
		startPosition = getStartPosition("start_position");
		collisionMatrix = getCollisionMatrix("blocked");
		blockEntities = getBlockingEntities("blocked");
		switchEntities = getSwitchEntities("switch");
		destinationEntities = getDestinationEntities("destination");
		makeBaseSwitch();
	}

	@Override
	public void render(BasicGameState bgs, Graphics g) {
		map.render(mapX, mapY, Map.layer.TERRAIN.ordinal());
		map.render(mapX, mapY, Map.layer.DECORATE.ordinal());
	}
	
	@Override
	public void update(GameContainer gc, int delta) {
		
	}
	
	public static void addToDummyQueue(Dummy dummy) {
		dummyQueue.add(dummy);
	}
	
	public Dummy createADummy(Color color) throws SlickException {
		Dummy newDummy;
		DummyBrain newBrain = new DummyBrain(this, new Vector2f(startPosition.getX(), startPosition.getY()));
		newDummy = new Dummy(this, Role.DUMMY, getStartPosition("start_position"), newBrain, color);
		return newDummy;
	}
	
	public Shape getStartPosition(String prop) {
		Shape start = null;
		
		for(int tx=0; tx<map.getWidth(); tx++) {
			for(int ty=0; ty<map.getHeight(); ty++) {
				tileID = map.getTileId(tx, ty, layer.ENTITY.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if("true".equals(value)) {
					int xrec = mapX + tx * map.getTileWidth();
					int yrec = mapY + ty * map.getTileHeight();
					Rectangle rect = new Rectangle(xrec, yrec, map.getTileWidth(), map.getTileHeight());
					start = rect;
				}
			}
		}
		return start;
	}
	public Vector<Body> getBlockingEntities(String prop) {
		Vector<Body> ent = new Vector<Body>();
		
		for(int tx=0; tx<map.getWidth(); tx++) {
			for(int ty=0; ty<map.getHeight(); ty++) {
				tileID = map.getTileId(tx, ty, layer.TERRAIN.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if("true".equals(value)) {
					int xrec = mapX + tx * map.getTileWidth();
					int yrec = mapY + ty * map.getTileHeight();
					Rectangle rect = new Rectangle(xrec, yrec, map.getTileWidth(), map.getTileHeight());
					Block block = new Block(Role.BLOCK, rect);
					ent.add(block);
				}
			}
		}
		return ent;
	}
	
	public Vector<Body> getDestinationEntities(String prop) throws SlickException {
		Vector<Body> ent = new Vector<Body>();
		
		for(int tx=0; tx<map.getWidth(); tx++) {
			for(int ty=0; ty<map.getHeight(); ty++) {
				tileID = map.getTileId(tx, ty, layer.ENTITY.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if("true".equals(value)) {
					int xrec = mapX + tx * map.getTileWidth();
					int yrec = mapY + ty * map.getTileHeight();
					Rectangle rect = new Rectangle(xrec, yrec, map.getTileWidth(), map.getTileHeight());
					Image image = map.getTileImage(tx, ty, layer.ENTITY.ordinal());
					Destination des = null;
					String[] colorProp = {"yellow", "red", "blue", "green"};
					for(int i=0; i<colorProp.length; i++) {
						String color = map.getTileProperty(tileID, colorProp[i], "false");
						if("true".equals(color)) {
							switch(colorProp[i]) {
							case "yellow":
								des = new Destination(Role.DESTINATION, rect, image, Color.yellow);
								addToDummyQueue(createADummy(Color.yellow));
								break;
							case "red":
								des = new Destination(Role.DESTINATION, rect, image, Color.red);
								addToDummyQueue(createADummy(Color.red));
								break;
							case "blue":
								des = new Destination(Role.DESTINATION, rect, image, Color.blue);
								addToDummyQueue(createADummy(Color.blue));
								break;
							case "green":
								des = new Destination(Role.DESTINATION, rect, image, Color.green);
								addToDummyQueue(createADummy(Color.green));
								break;
							}
						}
					}
					ent.add(des);
				}
			}
		}
		return ent;
	}
	public void makeBaseSwitch() {
		ArrayList<Integer> pathLengths = new ArrayList<Integer>();
		ArrayList<Path> paths = new ArrayList<Path>();
		Path p;
		int index = -1;
		int sx = (int) (startPosition.getX()-mapX)/32;
		int sy = (int) (startPosition.getY()-mapY)/32;
		for(int i=0; i<switchEntities.size(); i++) {
			int tx = (int) (switchEntities.get(i).getX() - mapX)/32;
			int ty = (int) (switchEntities.get(i).getY() - mapY)/32;
			p = getUpdatedPath(sx, sy, tx, ty);
			paths.add(p);
			pathLengths.add(p.getLength());
		}
		int minLength = Collections.min(pathLengths);
		index = pathLengths.indexOf(minLength);
		Switch s = (Switch) switchEntities.get(index);
		p = paths.get(index);
		int dx = p.getStep(p.getLength()-2).getX() - (int) (s.getX()-mapX)/32;
		int dy = p.getStep(p.getLength()-2).getY() - (int) (s.getY()-mapY)/32;
		if(dx == 0 && dy == 1) s.addBlockedDirection(Switch.direction.down.ordinal());
		else if(dx == 0 && dy == -1) s.addBlockedDirection(Switch.direction.up.ordinal());
		else if(dx == 1 && dy == 0) s.addBlockedDirection(Switch.direction.right.ordinal());
		else if(dx == -1 && dy == 0) s.addBlockedDirection(Switch.direction.left.ordinal());
	}
	public Vector<Body> getSwitchEntities(String prop) {
		Vector<Body> ent = new Vector<Body>();
		for(int tx=0; tx<map.getWidth(); tx++) {
			for(int ty=0; ty<map.getHeight(); ty++) {
				tileID = map.getTileId(tx, ty, layer.ENTITY.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if("true".equals(value)){
					int xrec = mapX + tx*map.getTileWidth();
					int yrec = mapY + ty*map.getTileHeight();
					Image image = map.getTileImage(tx, ty, layer.ENTITY.ordinal());
					Rectangle rect = new Rectangle(xrec, yrec, map.getTileWidth(), map.getTileHeight());
					Switch s = null;
					String direction = map.getTileProperty(tileID, "direction", "false");
					switch(direction) {
					case "up":
						s = new Switch(this, Role.SWITCH, rect, image, "up");
						break;
					case "down":
						s = new Switch(this, Role.SWITCH, rect, image, "down");
						break;
					case "left":
						s = new Switch(this, Role.SWITCH, rect, image, "left");
						break;
					case "right":
						s = new Switch(this, Role.SWITCH, rect, image, "right");
						break;
					}
					ent.add(s);
				}
			}
		}
		return ent;
	}

	public boolean[][] getCollisionMatrix(String prop) {
		boolean[][] block = new boolean[map.getWidth()][map.getHeight()];
		
		for(int tx=0; tx<map.getWidth(); tx++) {
			for(int ty=0; ty<map.getHeight(); ty++) {
				tileID = map.getTileId(tx, ty, layer.TERRAIN.ordinal());
				String value = map.getTileProperty(tileID, prop, "false");
				if("true".equals(value)) {
					block[tx][ty] = true;
				}
			}
		}
		return block;
	}
	
	public Path getUpdatedPath(int sx, int sy, int tx,int ty) throws NullPointerException{
		Path path;
		Mover dummyMover = new Mover(){
			
		};
		AStarPathFinder finder = new AStarPathFinder(this, 1000, false);
		path = finder.findPath(dummyMover, sx, sy, tx, ty);
		return path;
	}
	@Override
	public boolean blocked(PathFindingContext pfc, int x, int y) {
		return collisionMatrix[x][y];
	}
	@Override
	public float getCost(PathFindingContext context, int x, int y) {
		return 0;
	}
	@Override
	public int getHeightInTiles() {
		return map.getHeight();
	}
	@Override
	public int getWidthInTiles() {
		return map.getWidth();
	}
	@Override
	public void pathFinderVisited(int x, int y) {
		
	}
	@Override
	public Role getRole() {
		return Role.MAP;
	}
	@Override
	public void addToLevel(Level l) {
		l.add(this);

		for(int i=0; i<blockEntities.size(); i++) {
			blockEntities.get(i).addToLevel(l);
		}
		for(int i=0; i<destinationEntities.size(); i++) {
			destinationEntities.get(i).addToLevel(l);
		}
		for(int i=0; i<switchEntities.size(); i++) {
			switchEntities.get(i).addToLevel(l);
		}
		dummyQueue.poll().addToLevel(l);
	}
	@Override
	public void removeFromLevel(Level l) {
		l.remove(this);
	}
	@Override
	public void onCollision(Entity obstacle) {
		//Nothing happen
	}
	@Override
	public boolean isToRemove() {
		return false;
	}
}
